package spark.network.protocol;

import spark.network.Util;

public abstract class Base {
	//TODO : this may change depending on what the final protocol size limit will be
	public static final int MAX_BUFFER_SIZE = 520;

	/**
	 * Prefix consists out of an integer and the string BB1 for protocol version 1
	 */
	public static final int PREFIX_INTEGER = Integer.MAX_VALUE;
	public static final int PREFIX_INTEGER_OFFSET = 0;
	public static final int PREFIX_INTEGER_LENGTH = Integer.SIZE/8;
	public static final byte[] PREFIX_IDENT = "BB1".getBytes();
	public static final int PREFIX_IDENT_OFFSET = PREFIX_INTEGER_LENGTH;
	public static final int PREFIX_IDENT_LENGTH = PREFIX_IDENT.length;

	/**
	 * Type of the packet
	 */
	private byte[] typeBytes;
	public static final int TYPE_OFFSET = PREFIX_IDENT_OFFSET + PREFIX_IDENT_LENGTH;
	public static final int TYPE_LENGTH = Short.SIZE/8;

	/**
	 * Id of the packet, unique within packets sent from the server to the client
	 * however a server may receive packets with the same id from 2 different clients
	 * because of that client to server packets are additionaly identified by the clients session id
	 */
	private long packetId;
	public static final int PACKET_ID_OFFSET = TYPE_OFFSET + TYPE_LENGTH;
	public static final int PACKET_ID_LENGTH = Long.SIZE/8;

	/**
	 * Session id which the server assigns to the client and hence forth is used to identify the client
	 * when querying the server and vice versa
	 */
	private long session;
	public static final int SESSION_OFFSET = PACKET_ID_OFFSET + PACKET_ID_LENGTH;
	public static final int SESSION_LENGTH = Long.SIZE/8;

	private byte reliable;
	public static final int RELIABLE_OFFSET = SESSION_OFFSET + SESSION_LENGTH;
	public static final int RELIABLE_LENGTH = Byte.SIZE/8;

	private long timeStampSend;
	public static final int TIME_STAMP_SEND_OFFSET = RELIABLE_OFFSET + RELIABLE_LENGTH;
	public static final int TIME_STAMP_SEND_LENGTH = Long.SIZE/8;

	private long timeStampReceive;

	protected static final int SIZE =
			PREFIX_INTEGER_LENGTH +
	        PREFIX_IDENT_LENGTH +
	        TYPE_LENGTH +
	        PACKET_ID_LENGTH +
			SESSION_LENGTH +
			RELIABLE_LENGTH +
			TIME_STAMP_SEND_LENGTH;

	protected Base(boolean isReliable, byte[] typeBytes) {
		reliable = (byte)((isReliable) ? 1 : 0);
		this.typeBytes = typeBytes;
	}

	public long getPacketId() {
		return packetId;
	}

	public void setPacketId(long packetId) {
		this.packetId = packetId;
	}

	public long getSession() {
		return session;
	}

	public void setSession(long session) {
		this.session = session;
	}

	public long getTimeStampSend() {
		return timeStampSend;
	}

	public void setTimeStampSend(long timeStampSend) {
		this.timeStampSend = timeStampSend;
	}

	public long getTimeStampReceive() {
		return timeStampReceive;
	}

	public void setTimeStampReceive(long timeStampReceive) {
		this.timeStampReceive = timeStampReceive;
	}

	public static boolean isPrefixValid(byte[] data) {
		return
				Util.makeInt(data, PREFIX_INTEGER_OFFSET) == PREFIX_INTEGER &&
	            data[PREFIX_IDENT_OFFSET] == PREFIX_IDENT[0] &&
	            data[PREFIX_IDENT_OFFSET + 1] == PREFIX_IDENT[1] &&
	            data[PREFIX_IDENT_OFFSET + 2] == PREFIX_IDENT[2];
	}

	public static short getType(byte[] data) {
		return Util.makeShort(data, TYPE_OFFSET);
	}

	/**
	 * Fills a provided byte array with the binary data representing the packet.
	 * The buffer must be of a sufficient size.
	 * @param buffer Out parameter which is filled with he contents of the package.
	 * @throws IndexOutOfBoundsException When a too small buffer is provided.
	 */
	public void getBytes(byte[] buffer) {
		Util.arraycopy(Util.makeBytes(Base.PREFIX_INTEGER), 0, buffer, PREFIX_INTEGER_OFFSET);
		Util.arraycopy(PREFIX_IDENT, 0, buffer, PREFIX_IDENT_OFFSET);
		Util.arraycopy(typeBytes, 0, buffer, TYPE_OFFSET);
		Util.arraycopy(Util.makeBytes(packetId), 0, buffer, PACKET_ID_OFFSET);
		Util.arraycopy(Util.makeBytes(session), 0, buffer, SESSION_OFFSET);
		//Util.arraycopy(Util.makeBytes(challengeNumber), 0, buffer, CHALLENGE_NUMBER_OFFSET);
		buffer[RELIABLE_OFFSET] = reliable;
		Util.arraycopy(Util.makeBytes(timeStampSend), 0, buffer, TIME_STAMP_SEND_OFFSET);
	}

	/**
	 * Fills this packet with the data provided in the buffer.
	 * The buffer must be of a sufficient size.
	 * @param buffer Buffer containing the data needed to fill this packet.
	 * @throws IndexOutOfBoundsException When a too small buffer is provided.
	 */
	public void fill(byte[] buffer) {
		packetId = Util.makeLong(buffer, PACKET_ID_OFFSET);
		session = Util.makeLong(buffer, SESSION_OFFSET);
		//challengeNumber = Util.makeShort(buffer, CHALLENGE_NUMBER_OFFSET);
		reliable = buffer[RELIABLE_OFFSET];
		timeStampSend = Util.makeLong(buffer, TIME_STAMP_SEND_OFFSET);
	}

	/**
	 * Obtains this packet's size in bytes.
	 * @return Packet's size in bytes.
	 */
	public int getSize() {
		return SIZE;
	}

	protected void setReliable(byte reliable) {
		this.reliable = reliable;
	}

	public boolean isReliable() {
		//return challengeNumber != -1;
		return reliable != 0;
	}

	public long getRoundTripTime() {
		return timeStampReceive - timeStampSend;
	}
}
